package org.tigris.subversion.subclipse.graph.editors;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.tigris.subversion.subclipse.core.ISVNRemoteResource;
import org.tigris.subversion.subclipse.core.SVNException;
import org.tigris.subversion.subclipse.core.SVNProviderPlugin;
import org.tigris.subversion.subclipse.graph.Activator;
import org.tigris.subversion.subclipse.graph.cache.Cache;
import org.tigris.subversion.subclipse.graph.cache.Graph;
import org.tigris.subversion.subclipse.graph.cache.Node;
import org.tigris.subversion.subclipse.ui.operations.SVNOperation;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;
import org.tigris.subversion.svnclientadapter.ISVNInfo;
import org.tigris.subversion.svnclientadapter.SVNRevision;

public class GraphBackgroundTask extends SVNOperation {

  private IResource resource;
  private ISVNRemoteResource remoteResource;
  private GraphicalViewer viewer;
  private RevisionGraphEditor editor;
  private SVNRevision refreshRevision;
  private Node refreshNode;
  private SVNRevision[] refreshRevisions;
  private Node[] refreshNodes;
  private boolean includeMergedRevisions = false;
  private boolean getNewRevisions = true;
  private Graph graph;

  private static final int TOTAL_STEPS = Integer.MAX_VALUE;
  private static final int SHORT_TASK_STEPS = TOTAL_STEPS / 50; // 2%
  private static final int VERY_LONG_TASK = TOTAL_STEPS / 2; // 50%
  private static final int TASK_STEPS = (TOTAL_STEPS - SHORT_TASK_STEPS * 3 - VERY_LONG_TASK) / 2;

  protected GraphBackgroundTask(
      IWorkbenchPart part, GraphicalViewer viewer, RevisionGraphEditor editor) {
    super(part);
    this.viewer = viewer;
    this.editor = editor;
  }

  public GraphBackgroundTask(
      IWorkbenchPart part, GraphicalViewer viewer, RevisionGraphEditor editor, IResource resource) {
    this(part, viewer, editor);
    this.resource = resource;
  }

  public GraphBackgroundTask(
      IWorkbenchPart part,
      GraphicalViewer viewer,
      RevisionGraphEditor editor,
      ISVNRemoteResource remoteResource) {
    this(part, viewer, editor);
    this.remoteResource = remoteResource;
  }

  public void setGetNewRevisions(boolean getNewRevisions) {
    this.getNewRevisions = getNewRevisions;
  }

  protected void execute(IProgressMonitor monitor) throws SVNException, InterruptedException {
    boolean error = false;
    Cache cache = null;
    monitor.beginTask("Calculating graph information", TOTAL_STEPS);
    monitor.worked(SHORT_TASK_STEPS);
    ISVNClientAdapter client = null;
    try {
      client = SVNProviderPlugin.getPlugin().getSVNClient();
      ISVNInfo info;
      if (resource == null) info = client.getInfo(remoteResource.getUrl());
      else {
        if (resource.getRawLocation() == null)
          info = client.getInfoFromWorkingCopy(resource.getLocation().toFile());
        else info = client.getInfoFromWorkingCopy(resource.getRawLocation().toFile());
        if (info.getUuid() == null) {
          info = client.getInfo(info.getUrl());
        }
      }

      if (editor != null) ((RevisionGraphEditorInput) editor.getEditorInput()).setInfo(info);

      long revision = info.getRevision().getNumber();
      String path = info.getUrl().toString().substring(info.getRepository().toString().length());

      monitor.setTaskName("Initializating cache");
      cache = getCache(info.getUuid());
      monitor.worked(SHORT_TASK_STEPS);

      // update the cache
      long latestRevisionStored = cache.getLatestRevision();
      SVNRevision latest = null;
      SVNRevision endRevision = null;
      monitor.setTaskName("Connecting to the repository");
      // TODO: try-catch this line and make it work off-line
      long latestRevisionInRepository =
          client.getInfo(info.getRepository()).getLastChangedRevision().getNumber();
      monitor.worked(SHORT_TASK_STEPS);

      if (refreshRevision != null
          || refreshRevisions != null
          || latestRevisionInRepository > latestRevisionStored) {
        if (refreshRevision == null) {
          if (latestRevisionStored >= latestRevisionInRepository)
            latest = new SVNRevision.Number(latestRevisionInRepository);
          else latest = new SVNRevision.Number(latestRevisionStored + 1);
        } else {
          latest = refreshRevision;
        }

        if (refreshRevision == null) endRevision = SVNRevision.HEAD;
        else endRevision = refreshRevision;

        try {
          monitor.setTaskName("Retrieving revision history");
          int unitWork;
          if (refreshRevision == null && refreshRevisions == null)
            unitWork = VERY_LONG_TASK / (int) (latestRevisionInRepository - latestRevisionStored);
          else if (refreshRevisions != null) unitWork = VERY_LONG_TASK / refreshRevisions.length;
          else unitWork = VERY_LONG_TASK;
          if (refreshRevisions != null) {
            if (monitor.isCanceled()) return;
            monitor.setTaskName("Refreshing cache");
            List refreshedNodes = new ArrayList();
            for (int i = 0; i < refreshNodes.length; i++) {
              if (refreshNodes[i].getAction() != 'D') refreshedNodes.add(refreshNodes[i]);
            }
            cache.refresh(refreshedNodes, info, monitor, unitWork);
          } else if (refreshRevision != null) {
            if (monitor.isCanceled()) return;
            monitor.setTaskName("Refreshing cache");
            revision = refreshNode.getRevision();
            path = refreshNode.getPath();
            List refreshedNodes = new ArrayList();
            refreshedNodes.add(refreshNode);
            cache.refresh(refreshedNodes, info, monitor, unitWork);
          }
          if (getNewRevisions) {
            CallbackUpdater callbackUpdater = new CallbackUpdater(cache, monitor, unitWork, client);
            cache.startUpdate();
            client.getLogMessages(
                info.getRepository(),
                latest,
                latest,
                endRevision,
                false,
                true,
                0,
                includeMergedRevisions,
                ISVNClientAdapter.DEFAULT_LOG_PROPERTIES,
                callbackUpdater);
            cache.finishUpdate();
          }
        } catch (Exception e) {
          Activator.handleError(e);
          error = true;
          Activator.showErrorDialog("Calculate Revision Graph Information", e, false);
        }
      } else {
        monitor.worked(VERY_LONG_TASK);
      }
      if (editor != null) {
        if (error == true || monitor.isCanceled()) {
          if (refreshRevision == null && refreshRevisions == null) {
            Display.getDefault()
                .syncExec(
                    new Runnable() {
                      public void run() {
                        IWorkbenchWindow window = editor.getEditorSite().getWorkbenchWindow();
                        IWorkbenchPage page = window.getActivePage();
                        page.activate(editor);
                        page.closeEditor(editor, false);
                      }
                    });
          }
        } else {
          updateView(monitor, cache, path, revision);
        }
      }
      monitor.done();
    } catch (Exception e) {
      Activator.handleError(e);
      Activator.showErrorDialog("Calculate Revision Graph Information", e, false);
      return;
    } finally {
      if (cache != null) cache.close();
      SVNProviderPlugin.getPlugin().getSVNClientManager().returnSVNClient(client);
    }
  }

  //	private void serialize(Graph graph) {
  //		try {
  //			FileOutputStream fos = new FileOutputStream("c:/sample-graph");
  //			ObjectOutputStream oos = new ObjectOutputStream(fos);
  //			oos.writeObject(graph);
  //			fos.close();
  //		} catch (Exception e) {
  //			// TODO Auto-generated catch block
  //			e.printStackTrace();
  //		}
  //	}

  private void updateView(IProgressMonitor monitor, Cache cache, String path, long revision) {
    monitor.setTaskName("Finding root node");

    int unitWork = TASK_STEPS / (int) (revision);
    if (unitWork < 1) unitWork = 1;
    Node root = cache.findRootNode(path, revision, new WorkMonitorListener(monitor, unitWork));

    monitor.setTaskName("Calculating graph");
    if (revision == root.getRevision()) unitWork = TASK_STEPS;
    else unitWork = TASK_STEPS / (int) (revision - root.getRevision());
    if (unitWork < 1) unitWork = 1;
    graph =
        cache.createGraph(
            root.getPath(), root.getRevision(), new WorkMonitorListener(monitor, unitWork));
    graph.setSelectedPath(path);
    graph.setSelectedRevision(revision);
    monitor.setTaskName("Drawing graph");

    Display.getDefault()
        .syncExec(
            new Runnable() {
              public void run() {
                viewer.setContents(graph);
              }
            });
  }

  private Cache getCache(String uuid) {
    File f = Cache.getCacheDirectory(resource);
    if (refreshRevision == null) return new Cache(f, uuid);
    else return new Cache(f, uuid, Long.parseLong(refreshRevision.toString()));
  }

  protected String getTaskName() {
    return "Calculating graph information";
  }

  public void setRefreshRevision(SVNRevision refreshRevision, Node refreshNode) {
    this.refreshRevision = refreshRevision;
    this.refreshNode = refreshNode;
    includeMergedRevisions = refreshRevision != null;
  }

  public void setRefreshRevisions(SVNRevision[] refreshRevisions, Node[] refreshNodes) {
    this.refreshRevisions = refreshRevisions;
    this.refreshNodes = refreshNodes;
    includeMergedRevisions = refreshRevisions != null;
  }

  public void setIncludeMergedRevisions(boolean includeMergedRevisions) {
    this.includeMergedRevisions = includeMergedRevisions;
  }
}
